use std::process::Command;
struct CommandExecutor{
    program: String,
    args: Option<String>,
    envs: std::collections::HashMap<String, String>,
    working_dir: Option<String>,
}

impl CommandExecutor {
    fn new<S: Into<String>>(program: S) -> Self {
        CommandExecutor {
            program: program.into(),
            args: None,
            envs: std::collections::HashMap::new(),
            working_dir: None,
        }
    }

    fn arg<S: Into<String>>(&mut self, arg: S) -> &mut Self{
        self.args = Some(arg.into());
        self
    }

    fn env<S: Into<String>>(&mut self, key: S, value: S) -> &mut Self{
        self.envs.insert(key.into(), value.into());
        self
    }

    fn working_dir<S: Into<String>>(&mut self, dir: S) -> &mut Self{
        self.working_dir = Some(dir.into());
        self
    }

    fn execute(&self) -> Result<String, Box<dyn std::error::Error>> {
        let mut command = Command::new(&self.program);
        if let Some(arg) = &self.args {command.arg(&arg);}
        for (k, v) in &self.envs {
            command.env(k, v);
        }
        if let Some(dir) = &self.working_dir {
            command.current_dir(dir);
        }
        let output = command.output()?;
        if output.status.success() {
            Ok(String::from_utf8_lossy(&output.stdout).into_owned())
        } else {
            Err(format!("Command failed: {}", String::from_utf8_lossy(&output.stderr).into_owned()).into())
        }
    }

}
fn main() {
    let mut ce = CommandExecutor::new("ls");
    ce.env("MY_VAR","my_value");
    ce.working_dir("/tmp");
    ce.arg("-l");
    let res = ce.execute().unwrap();
    println!("{}", res);
}
